package pl.put.to.classification.SGD;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import pl.put.to.classification.data.DataInstance;
import pl.put.to.classification.data.DataSet;
import pl.put.to.classification.utils.MatrixUtils;
import pl.put.to.classification.verification.StopConditionVeryficator;
import pl.put.to.classification.verification.error.IErrorVeryficator;
import Jama.Matrix;

public class ZawiasSGDClassification {

	private double[] weights;
	private Matrix weightsVector;
	private IErrorVeryficator errorVeryficator;
	private StopConditionVeryficator stopConditionVeryficator;
	
	public ZawiasSGDClassification(IErrorVeryficator errorVeryficator) {
		this.errorVeryficator = errorVeryficator;
		this.stopConditionVeryficator = new StopConditionVeryficator();
	}
	
	public void train(DataSet trainSet, DataSet verificationSet) throws IOException {
		double previousError = 0;
		double error = 0;
		
		boolean stop = false;
		
		long startTime = System.currentTimeMillis();
		long stopTime;
		weightsVector = prepareInitialWeightsVector(trainSet).transpose();
		
		double k = 0.0;
		double alpha_k;
		
		trainSet.standarizeData();
		
		List<DataInstance> inst = new ArrayList<DataInstance>();
		for (DataInstance in : trainSet.getInstances()) {
			inst.add(in);
		}
		
		DataSet data = new DataSet(inst);
		
		while (!stop) {
			k++;
			alpha_k = 0.1 / Math.sqrt(k);
			
			if  (data.getNumberOfInstances() < 1) {
				inst = new ArrayList<DataInstance>();
				for (DataInstance in : trainSet.getInstances()) {
					inst.add(in);
				}
				data = new DataSet(inst);
			}
			
			DataInstance instance = data.getRandomInstance();
		
			if (checkCondition(instance, weightsVector)) {
				Matrix x = MatrixUtils.convertToVector(instance.getVariableValues()).transpose();
				int y = DataSet.determineY(instance.getClassVariableValue());
				
				weightsVector = weightsVector.plus(x.times(y).times(alpha_k));
			} else {
				System.out.println("CONTINUE");
				continue;
			}
			
			error = errorVeryficator.countError(trainSet, weightsVector);
			
			stopTime = System.currentTimeMillis();
			
			System.out.println("time: " + (stopTime - startTime));
			System.out.println("train error: " + error);
			System.out.println("test error: " + test(verificationSet));
			System.out.println();
			
			stop = stopConditionVeryficator.verifyIfErrorNotChanging(error, previousError);
			
			previousError = error;
			
			if (k >= 10000) {
				System.out.println("STOP by max iteration");
				stop = true;
			}
		}
		weights = MatrixUtils.convertToVectorArray(weightsVector.transpose());
	}
	
	public double test(DataSet testSet) {
		double error = errorVeryficator.countError(testSet, weightsVector);
		
		return error;
	}
	
	public double[] getWeights() {
		return weights;
	}
	
	private Matrix prepareInitialWeightsVector(DataSet data) {
		double[][] wArray = new double[1][data.getNumberOfVariables()];
		Arrays.fill(wArray[0], 0);
		Matrix w = new Matrix(wArray);
		
		return w;
	}
	
	private boolean checkCondition(DataInstance instance, Matrix weights) {
		boolean result;
		
		int y = DataSet.determineY(instance.getClassVariableValue());
		Matrix x = MatrixUtils.convertToVector(instance.getVariableValues()).transpose();
		
		double condition = MatrixUtils.convertToScalar(weights.transpose().times(x).times(y));
		
		result = condition <= 1 ? true : false;
		
		return result;
	}
}
